/* 
 * File:   VulnerabilidadService.cpp
 * Author: Maximiliano Monzon (82341)
 * 
 * Created on October 22, 2012, 4:58 AM
 */
#include <iomanip>
#include <string.h>
#include <sstream>
#include <vector>
#include <arpa/inet.h>
#include <cmath>
#include "server_CriptografiaService.h"
#include "common_ProtocoloConstantes.h"
#include "common_Codec.h"
#include "server_Proceso.h"
#include "common_MutexScopeLocker.h"

using namespace std;
using namespace TP;
using namespace TP::Servicios;

#define COMANDO_INVALIDO 0
#define IP_INVALIDA 0

CriptografiaService::CriptografiaService( const string& pRutaArchivoEncriptado, const int& pLongitudClave, const int &pCapacidadClientesMaxima, ostream* pOutput) {
    
    _rutaArchivoEncriptado = pRutaArchivoEncriptado;
    _longitudClave = pLongitudClave;
    _capacidadClientesMaxima = pCapacidadClientesMaxima;
    _output = pOutput;
}
        
CriptografiaService::~CriptografiaService() {}

void CriptografiaService::Iniciar() throw (Excepcion&){
    try
    {
        // Inicio la carga del archivo encriptado.
        
        ifstream input (_rutaArchivoEncriptado.c_str(), ios::in|ios::binary);
        if (!input.is_open()) {
            string msg = "No se puede leer el archivo encriptado. Ruta: \"";
            msg += _rutaArchivoEncriptado + "\"";
            
            throw Excepcion (msg);
        }

        // Codifico el archivo.
        
        uint8_t* buffer = new uint8_t [256];
        Codec codec;
        _data = "";
        string contentAux = "";
        
        while (!input.eof())
        {
            bzero (buffer, 256);
            input.read(reinterpret_cast <char*> (buffer), 256);
            _data += codec.codificar(contentAux, buffer, input.gcount());
        }
        
        delete [] buffer;
        input.close();
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("No se puede inicializar el servicio de criptografia.");
        throw;
    }
}

void CriptografiaService::Finalizar() throw (Excepcion&){

    try
    {
        // Se imprime por salida el estado del servicio.
        this->imprimirEstado();
        
        // Se finaliza el servicio.
        for (vector<Proceso*>::iterator it = _procesos.begin(); it < _procesos.end(); ++it)
        {
            delete (*it);
        }
        
        _procesos.clear();
        _claves.clear();
    }
    catch (Excepcion& pEx)
    {
        pEx.expandirMensaje("No se puede finalizar correctamente el servicio de criptografia.");
        throw;
    }
}

void CriptografiaService::mensajeEntranteManejador(IMensajero* pMensajero, const string & pMensaje){

    // El valor de retorno es error hasta que se demuestre lo contrario.
    string return_msg = ERROR;

    try
    {
        // Hidrato el comando.

        std::stringstream ssParametros (pMensaje);
        string strParametro = "";
        vector<string> comando;
        while (std::getline(ssParametros, strParametro, SEPARADOR_CAMPO))
        {
            std::stringstream ssHelper;
            ssHelper << strParametro;
            strParametro = "";
            ssHelper >> strParametro;
            
            if (strParametro.length () > 0)
            {
                comando.push_back(strParametro);
            }
        }

        if (comando.size() == 0)
        {
            // Comando no definido.
            throw COMANDO_INVALIDO;
        }
        
        // Se determina a que comando corresponde el mensaje.
        if (PING == comando [0])
        {
            this->responderEco (pMensajero, comando);
        } 
        else if (GET_JOB_PART == comando[0])
        {
            this->getJobPart (pMensajero, comando);
        }
        else if (POSSIBLE_KEY == comando[0])
        {
            this->setPossibleKey (pMensajero, comando);
        }
        else if (JOB_PART_FINISHED == comando[0])
        {
            this->notifyJobPartFinished (pMensajero, comando);
        }
        else
        {
            // Comando no definido => Error.
            throw COMANDO_INVALIDO;
        }
    }
    catch (int pEx)
    {
        try
        {
            if (COMANDO_INVALIDO == pEx)
                return_msg += SEPARADOR_CAMPO + "Comando invalido.";
            else
                return_msg += SEPARADOR_CAMPO + "Error desconocido.";
            
            pMensajero->Responder(return_msg);
        }
        catch (Excepcion& pEx)
        {
            cerr << pEx.what() << endl;
        }
    }
}

bool CriptografiaService::ipValida (const string& pIp) {

    char str_aux [256];

    bool ip_valida = inet_pton(AF_INET, pIp.c_str(), str_aux);

    if (!ip_valida)
        ip_valida = inet_pton(AF_INET6, pIp.c_str(), str_aux);
    
    return ip_valida;
}

void CriptografiaService::calcularRangoClaves (uint32_t &claveInicial
                                             , uint32_t &claveFinal
                                             , uint32_t &numeroCliente)
{
    if (0 <= numeroCliente && numeroCliente < _capacidadClientesMaxima - 1)
    {
      claveInicial = numeroCliente * floor (pow (10,_longitudClave) / _capacidadClientesMaxima);
      claveFinal = (numeroCliente + 1) * floor (pow (10,_longitudClave) / _capacidadClientesMaxima) - 1;
    }
    else if (numeroCliente == _capacidadClientesMaxima - 1 )
    {
      claveInicial = (_capacidadClientesMaxima - 1) * floor (pow (10,_longitudClave) / _capacidadClientesMaxima);
      claveFinal = pow (10,_longitudClave) - 1;
    }
}

void CriptografiaService::responderEco(IMensajero* pMensajero, vector<string> &pComando) {
    pMensajero->Responder(OK);
}

void CriptografiaService::getJobPart (IMensajero* pMensajero, vector<string> &pComando){
    
    MutexScopeLocker locker(_mutex2);
    
    try
    {
        string notificacion;

        if ( _procesos.size() < _capacidadClientesMaxima )
        {
            // Se asigna al cliente un rango de claves y se le notifica el trabajo.
            // Formato del mensaje: 
            // JOB-PART <mensaje-encriptado> <num_parte> <num_digitos> <clave_inicial> <clave_final> 

            Proceso* proceso = new Proceso (_procesos.size());
            proceso->finalizado(false);
            _procesos.push_back(proceso);

            notificacion = JOB_PART;
            notificacion += SEPARADOR_CAMPO + _data;

            std::stringstream strm;
            strm << proceso->getId();
            string aux = "";
            strm >> aux;
            notificacion += SEPARADOR_CAMPO + aux;

            strm.clear();
            strm << _longitudClave;
            aux = "";
            strm >> aux;
            notificacion += SEPARADOR_CAMPO + aux;

            uint32_t claveInicial, claveFinal;
            calcularRangoClaves (claveInicial, claveFinal, proceso->getId());

            strm.clear();
            strm << claveInicial;
            aux = "";
            strm >> aux;

            strm.clear();
            string aux2 = "";
            strm << claveFinal;
            strm >> aux2;

            notificacion += SEPARADOR_CAMPO + aux;
            notificacion += SEPARADOR_CAMPO + aux2;

            // A fin de cumplir con las especificaciones del protocolo.
            notificacion += SEPARADOR_CAMPO;
            notificacion += FIN_NOTIFICACION;
        }
        else
        {
            // El cupo de clientes se encuentra lleno.  Se rechaza la solicitud. Se
            // notifica al cliente la situacion.
            notificacion = NO_JOB_PART;

            // A fin de cumplir con las especificaciones del protocolo.
            notificacion += SEPARADOR_CAMPO;
            notificacion += FIN_NOTIFICACION;

        }

        pMensajero->Responder(notificacion);
    }
    catch (Excepcion &ex)
    {
        cerr << endl << ex.what();
    }
}

void CriptografiaService::setPossibleKey (IMensajero* pMensajero, vector<string> &pComando){
    
    try
    {
        MutexScopeLocker locker(_mutex2);
        
        if (pComando.size() > 1)
        {
            if (this->esPosibleClave(pComando[1]))
            {
                _claves.push_back(pComando[1]);
            }
        }
        
        string mensaje = OK;
        pMensajero->Responder(mensaje);

    }
    catch (Excepcion &ex)
    {
        cerr << endl << ex.what();
    }
}

void CriptografiaService::notifyJobPartFinished (IMensajero* pMensajero, vector<string> &pComando){
    
    MutexScopeLocker locker(_mutex2);
    
    try
    {
        std::stringstream strm (pComando[1]);
        uint32_t idProceso = -1;
        strm >> idProceso;
        
        for (vector<Proceso*>::iterator it = _procesos.begin(); it < _procesos.end(); ++it)
        {
            if ((*it)->getId() == idProceso)
            {
                (*it)->finalizado(true);
            }
        }
        
        string mensaje = OK;
        pMensajero->Responder(mensaje);
    }
    catch (Excepcion &ex)
    {
        cerr << endl << ex.what();
    }
}

void CriptografiaService::imprimirEstado(){

    /* Para que un proceso se considere finalizado se debe haber trabajado con
     * al menos la capacidad maxima de clientes (el resto se ignoran para el 
     * proceso).
     * 
     */
    bool finalizado = (_procesos.size() >= _capacidadClientesMaxima);
    
    /* Verifico que todos los clientes hayan finalizado el trabajo.
     * 
     */
    for (vector<Proceso*>::iterator it = _procesos.begin(); it < _procesos.end() && finalizado ; ++it)
    {
        finalizado = (*it)->estaFinalizado();
    }
    
    if (finalizado)
    {
        if (_claves.size() == 0)
        {
            *_output << "No keys found" << endl;
        } else if (_claves.size() == 1)
        {
            *_output << _claves[0] << endl;
        }
        else if (_claves.size() > 1)
        {
            *_output << "Multiple keys found" << endl;
        }
    }
    else
    {
        *_output << "Not finished" << endl;
    }
}

void CriptografiaService::ed(uint8_t *data, size_t ndata, const uint8_t *key, size_t nkey){
    uint8_t i = 0, j = 0, s[256];
    do { s[i] = i; } while (++i);
    do {
        j += s[i] + key[i % nkey];
        i - j && (s[i] ^= s[j], s[j] ^= s[i], s[i] ^= s[j]);
    } while (++i);
    j = 0;
    while (ndata--) {
        i++; j += s[i];
        i - j && (s[i] ^= s[j], s[j] ^= s[i], s[i] ^= s[j]);
        *data++ ^= s[(s[i] + s[j]) & 0xff];
    }
}

bool CriptografiaService::esPosibleClave(const string& pClave){
    
    // Verifico si la clave actual es una posible clave.

    // Copio los datos a desencriptar en un buffer temporal.
    Codec codec;
    uint32_t bufferLength;
    uint8_t* buffer;
    codec.decodificar(buffer, bufferLength, _data);
    
    // Desencripto los datos con la clave.
    this->ed(buffer, bufferLength, reinterpret_cast <const uint8_t*> (pClave.c_str()), pClave.length());

    // Verifico si es interpretable la informacion.
    bool interpretable = this->dataInterpretable (buffer, bufferLength);

    delete [] buffer;
    
    return interpretable;
}

bool CriptografiaService::dataInterpretable (uint8_t *buffer, uint32_t pLongitud){

    bool interpretable = true;
    
    /* Si el buffer posee informacion interpretable, cada byte debe representar
     * un caracter ASCII (menor a 128).
     */
    
    for (uint32_t i = 0; i < pLongitud ; i++)
    {
        interpretable = interpretable && (buffer [i] <= 127);
    }
    
    return interpretable;
}